package com.gitee.wsl.struct.compare.similarity

import com.gitee.wsl.security.hash.MurmurHash
import com.ionspin.kotlin.bignum.integer.BigInteger
import kotlinx.atomicfu.locks.ReentrantLock
import kotlinx.atomicfu.locks.withLock
import kotlin.jvm.JvmOverloads

/**
 *
 *
 * Simhash是一种局部敏感hash，用于海量文本去重。<br></br>
 * 算法实现来自：https://github.com/xlturing/Simhash4J
 *
 *
 *
 *
 * 局部敏感hash定义：假定两个字符串具有一定的相似性，在hash之后，仍然能保持这种相似性，就称之为局部敏感hash。
 *
 *
 * @author Looly, litaoxiao
 * @since 4.3.3
 */
class Simhash @JvmOverloads constructor(
    /** 存储段数，默认按照4段进行simhash存储  */
    private val fracCount: Int = 4,
    /** 汉明距离的衡量标准，小于此距离标准表示相似  */
    private val hammingThresh: Int = 3,
) {
    private val bitNum = 64
    private val fracBitNum: Int = bitNum / fracCount

    /** 按照分段存储simhash，查找更快速  */
    private val storage = ArrayList<MutableMap<String, MutableList<Long>>>(fracCount)
    private val lock = ReentrantLock()

    /**
     * 构造
     *
     * @param fracCount 存储段数
     * @param hammingThresh 汉明距离的衡量标准
     */
    /**
     * 构造
     */
    init {
        for (i in 0..<fracCount) {
            storage.add(HashMap())
        }
    }

    /**
     * 指定文本计算simhash值
     *
     * @param segList 分词的词列表
     * @return Hash值
     */
    fun hash(segList: Collection< CharSequence>): Long {
        val bitNum = this.bitNum
        // 按照词语的hash值，计算simHashWeight(低位对齐)
        val weight = IntArray(bitNum)
        var wordHash: Long
        for (seg in segList) {
            wordHash = MurmurHash.hash64(seg)
            for (i in 0..<bitNum) {
                if (((wordHash shr i) and 1L) == 1L) weight[i] += 1
                else weight[i] -= 1
            }
        }

        // 计算得到Simhash值
        val sb = StringBuilder()
        for (i in 0..<bitNum) {
            sb.append(if (weight[i] > 0) 1 else 0)
        }

        return BigInteger.parseString(sb.toString(), 2).longValue()
    }

    /**
     * 判断文本是否与已存储的数据重复
     *
     * @param segList 文本分词后的结果
     * @return 是否重复
     */
    fun equals(segList: Collection<CharSequence>): Boolean {
        val simhash = hash(segList)
        val fracList = splitSimhash(simhash)
        val hammingThresh = this.hammingThresh

        var frac: String?
        var fracMap: Map<String, List<Long>>
       this.lock.withLock {
           for (i in 0..<fracCount) {
               frac = fracList.get(i)
               fracMap = storage.get(i)
               if (fracMap.containsKey(frac)) {
                   for (simhash2 in fracMap.get(frac)!!) {
                       // 当汉明距离小于标准时相似
                       if (hamming(simhash, simhash2) < hammingThresh) {
                           return true
                       }
                   }
               }
           }
       }
//        val stamp: Long = this.lock.readLock()
//        try {
//            for (i in 0..<fracCount) {
//                frac = fracList.get(i)
//                fracMap = storage.get(i)
//                if (fracMap.containsKey(frac)) {
//                    for (simhash2 in fracMap.get(frac)!!) {
//                        // 当汉明距离小于标准时相似
//                        if (hamming(simhash, simhash2) < hammingThresh) {
//                            return true
//                        }
//                    }
//                }
//            }
//        } finally {
//            this.lock.unlockRead(stamp)
//        }
        return false
    }

    /**
     * 按照(frac, 《simhash, content》)索引进行存储
     *
     * @param simhash Simhash值
     */
    fun store(simhash: Long) {
        val fracCount = this.fracCount
        val storage = this.storage
        val lFrac = splitSimhash(simhash)

        var frac: String?
        var fracMap: Map<String, MutableList<Long>>
        lock.withLock {
           for (i in 0..<fracCount) {
                frac = lFrac.get(i)!!
                fracMap = storage.get(i)
                if (fracMap.containsKey(frac)) {
                    fracMap.get(frac)!!.add(simhash)
                } else {
                    val ls = ArrayList<Long>()
                    ls.add(simhash)
                    fracMap.put(frac, ls)
                }
            }
        }

//        val stamp: Long = this.lock.writeLock()
//        try {
//            for (i in 0..<fracCount) {
//                frac = lFrac.get(i)
//                fracMap = storage.get(i)!!
//                if (fracMap.containsKey(frac)) {
//                    fracMap.get(frac).add(simhash)
//                } else {
//                    val ls: List<Long?> = ArrayList()
//                    ls.add(simhash)
//                    fracMap.put(frac, ls)
//                }
//            }
//        } finally {
//            this.lock.unlockWrite(stamp)
//        }
    }

    //------------------------------------------------------------------------------------------------------ Private method start
    /**
     * 计算汉明距离
     *
     * @param s1 值1
     * @param s2 值2
     * @return 汉明距离
     */
    private fun hamming(s1: Long, s2: Long): Int {
        val bitNum = this.bitNum
        var dis = 0
        for (i in 0..<bitNum) {
            if ((s1 shr i and 1L) != (s2 shr i and 1L)) dis++
        }
        return dis
    }

    /**
     * 将simhash分成n段
     *
     * @param simhash Simhash值
     * @return N段Simhash
     */
    private fun splitSimhash(simhash: Long): List<String?> {
        val bitNum = this.bitNum
        val fracBitNum = this.fracBitNum

        val ls = ArrayList<String> ()
        val sb = StringBuilder()
        for (i in 0..<bitNum) {
            sb.append(simhash shr i and 1L)
            if ((i + 1) % fracBitNum == 0) {
                ls.add(sb.toString())
                sb.setLength(0)
            }
        }
        return ls
    } //------------------------------------------------------------------------------------------------------ Private method end
}
